package SSF.OS.NetFlow.Filter;

import com.renesys.raceway.SSF.*;
import com.renesys.raceway.DML.*;
import SSF.Net.Util.*;
import SSF.OS.NetFlow.*;


// Factor
// update: handle "record_type" correctly.

/* * 
 * It's composed either by a simple factor or a child filter.
 * <BR>
 * For a simple factor:
 * It specify a field where the data will be extract, the data type of it, and a
 * <B>range</B> that will be used to check whether the given data is in.
 * <BR>
 * For a chile filter:
 * It simply call the passFilter function of the child filter.
 * <BR>
 * Notice that NOT operation is also integrated here.
 * @see Filter
 * @see Term
 */
public class Factor implements BasicFilter
{
    private boolean denyFactor=false;
    private boolean simpleFactor=true;
    private Filter parentFilter=null;
    private Filter subFilter=null;
    private String fieldName=null;
    private Range range=null;
    private byte fieldType=Decoder.INT_TYPE;  //default
    private boolean recordTypeAssigned=false; //used if the field is "record_type"

    /**
     * Constructor.
     * <BR>Config a factor:
     * <UL>
     * <LI> action -- it's either "deny" or "permit"
     * <LI> filter -- it's not a simple factor, so this is a sub-filter included here.
     * <LI> field_name -- it's a simple factor, and this is the related field.
     * <LI> field_type -- the data type of the related field.
     * <LI> range -- the "range" that will be used in this simple factor.
     * </UL>
     * @param pFilter parent filter
     * @param cfg configuration of this factor.
     */
    public Factor(Filter pFilter, Configuration cfg) 
	throws configException
    {
	String evals;
	parentFilter = pFilter;

	//deny or not, that is, is there a "NOT" operation
	evals = (String)cfg.findSingle("action");
	if (evals!=null) {
	    if (evals.equals("deny"))
		denyFactor = true;
	    else if (!(evals.equals("permit")))
		System.err.println(evals+" is not valid value of action\n"+"Only \"permit\" and \"deny\" are allowed as action in the factors by now, default is permit");
	}

	//if this is actually a sub filter, config it and return
	Configuration subConfig = (Configuration)cfg.findSingle("filter");
	if (subConfig!=null) {
	    subFilter = new Filter(parentFilter, subConfig);
	    simpleFactor = false;
	    return;
	}

	//it's not a sub filter, but a "simple" factor, config it.

	//get field name
	fieldName = (String)cfg.findSingle("field_name");
	if (fieldName == null) {
	    System.err.println("the related field name must be specified for the factor");
	    throw new configException("No field name specified.");
	}

	//get field type, default is INT type
	evals = (String)cfg.findSingle("field_type");
	if (evals!=null) {
	    if (evals.equals("int"))
		fieldType = Decoder.INT_TYPE;
	    else if (evals.equals("short"))
		fieldType = Decoder.SHORT_TYPE;
	    else if (evals.equals("byte"))
		fieldType = Decoder.BYTE_TYPE;
	    else if (evals.equals("float"))
		fieldType = Decoder.FLOAT_TYPE;
	    else if (evals.equals("string"))
		fieldType = Decoder.STRING_TYPE;
	    else if (evals.equals("ip"))
		fieldType = Decoder.IP_TYPE;
	    else if (evals.equals("long"))
		fieldType = Decoder.LONG_TYPE;
	    else System.err.println("Error: undefined data type, check Decoder.java in the Filter package");
	}

	//config the range
	Configuration rangeConfig = (Configuration)cfg.findSingle("range");
	if (rangeConfig == null) {
	    System.err.println("In the factor, the range must be specified for the field :"+ fieldName);
	    throw new configException("No range specified");
	}
	else {
	    configRange(rangeConfig);
	}
    }

    /** whether the record satisfies this factor*/
    public boolean passFilter(FilterData record)
    {
	boolean result;

	//for debug
	//System.err.println("entering factor.passFilter");

	//if this is a simple factor
	if (simpleFactor) {
	    Object dObj = extractData(record);
	    if (dObj == null) {
		System.err.println("Warning: Field \""+fieldName
				   +"\" doesn't exist in record type \""
				   +parentFilter.player.getRecordTypeString(record.type)+"\"");
		return false;
	    }
	    else {
		//for debug
		//System.err.println("entering inRange");

		if (fieldName.equals("record_type")) {
		    if (recordTypeAssigned)  // got the recordID already.
			result = range.inRange(dObj, fieldType);
		    else {
			// this is not elegant here....
			// don't have the recordID yet.
			try {
			    int recID = parentFilter.player.getRecordTypeCode
				((String)((ExactRange)range).reference);
			    if (recID != -1) {
				recordTypeAssigned = true;
				((ExactRange)range).reference = new Integer(recID);
			    }
			    else {
				// by now, it SHOULD be there unless there is
				// no such type of record in this file at all.
				System.err.println("Warning: no record of type "+(String)((ExactRange)range).reference+" in this file.");
			    }
			}
			catch (Exception any) {
			    System.err.println("\'record_type\' is only supported in ExactRange, that is, use \'equals\' attribute in range");
			    System.err.println(any.getMessage());
			    System.exit(-1);
			}			
		    }
		}
		result = range.inRange(dObj, fieldType);

		//for debug
		//System.err.println("out inRange, result "+result);
	    }
	}
	else result = subFilter.passFilter(record);

	//whether there is a "NOT" operation of not.
	if (denyFactor)
	    return (!result);
	else return result;
    }

    //////////////////// private method//////////////////////////////////

    /**
     * extract data of specified field out from the record.
     */
    private Object extractData(FilterData record)
    {
	//is it "recordType"
	if (fieldName.equals("record_type"))
	    return new Integer(record.type);
	//is it "srcNIH"
	if (fieldName.equals("src_nhi"))
	    return parentFilter.player.getRecordSourceString(record.srcCode);
	//is it "timeStamp"
	if (fieldName.equals("time_stamp"))
	    return new Float(record.timeStamp);
	//is it "length"
	if (fieldName.equals("length"))
	    return new Integer(record.bytes.length);

	//use the corresponding decoder to decode and extract data.
	Decoder recordDecoder = 
	    (Decoder)parentFilter.decoderTable.get(new Integer(record.type));

	//decode
	if (recordDecoder == null) {
	    System.err.println("No decoder for the given record");
	    return null;
	}

	int index = recordDecoder.getFieldIndex(fieldName, fieldType);

	//if the field doesn't exist
	if (index == Decoder.FIELD_NOT_FOUND)
	    return null;

	// get the data when it exists.
	switch (fieldType) {
	case Decoder.INT_TYPE: 
	    return new Integer(BytesUtil.bytesToInt(record.bytes, index));
	case Decoder.SHORT_TYPE:
	    return new Short(BytesUtil.bytesToShort(record.bytes, index));
	case Decoder.BYTE_TYPE:
	    return new Byte(record.bytes[index]);
	case Decoder.FLOAT_TYPE:
	    return new Float(BytesUtil.bytesToFloat(record.bytes, index));
	case Decoder.STRING_TYPE:
	    return new String(BytesUtil.bytesToString(record.bytes, index));
	case Decoder.IP_TYPE:
	    return new String(IP_s.IPtoString
			      (BytesUtil.bytesToInt(record.bytes, index)));
	case Decoder.LONG_TYPE:
	    return new Long(BytesUtil.bytesToLong(record.bytes, index));

	default:
	    System.err.println("Factor.extractData(), fieldType undefined.");
	    return null;
	}
    }

    /**
     * config the range object to be used
     */
    private void configRange(Configuration cfg) throws configException
    {
	String evals;

	//config exact_range
	evals = (String)cfg.findSingle("equals");
	if (evals != null) {
	    range = new ExactRange();
	    range.config(cfg, fieldType);
	    return;
	}
	//record_type is only supported in ExactRange() !!!!!!!
	if (fieldName.equals("record_type")) {
	    throw new configException("record_type must use ExactRange, that is, use \'equals\' attribute to define range");
	}
       
	//config numerical range
	evals = (String)cfg.findSingle("min");
	if (evals != null) {
	    range = new NumericRange();
	    range.config(cfg, fieldType);
	    return;
	}
	else {
	    evals = (String)cfg.findSingle("max");
	    if (evals != null) {
		range = new NumericRange();
		range.config(cfg, fieldType);
		return;
	    }
	}

	//config regular expression range
	evals = (String)cfg.findSingle("reg_exp");
	if (evals != null) {
	  range = new RERange();
	  range.config(cfg, fieldType);
	  return;
	}

	//user defined range
	evals = (String)cfg.findSingle("use");
	if (evals == null) {
	    System.err.println("Error:Invalid range definition, if it's user defined range, please give the class name.");
	    throw new configException("Invalid range.");
	}
	else {
	    try {
		Class rangeClass = Class.forName(evals);
		Object rObj = rangeClass.newInstance();
		range = (Range)rObj;
	    }
	    catch (Exception any) {
		System.err.println("Invalid class name: "+evals);
		any.printStackTrace();
		throw new configException("Invalid Range Class");
	    }
	}

	range.config(cfg, fieldType);
    }
}
